1   /*
2    * Copyright (c) 1999, 2006, Oracle and/or its affiliates. All rights reserved.
3    * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4    *
5    * This code is free software; you can redistribute it and/or modify it
6    * under the terms of the GNU General Public License version 2 only, as
7    * published by the Free Software Foundation.  Oracle designates this
8    * particular file as subject to the "Classpath" exception as provided
9    * by Oracle in the LICENSE file that accompanied this code.
10   *
11   * This code is distributed in the hope that it will be useful, but WITHOUT
12   * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13   * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14   * version 2 for more details (a copy is included in the LICENSE file that
15   * accompanied this code).
16   *
17   * You should have received a copy of the GNU General Public License version
18   * 2 along with this work; if not, write to the Free Software Foundation,
19   * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20   *
21   * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22   * or visit www.oracle.com if you need additional information or have any
23   * questions.
24   */
25  
26  package sun.applet;
27  
28  import java.io.BufferedInputStream;
29  import java.io.File;
30  import java.io.FileInputStream;
31  import java.io.FileOutputStream;
32  import java.io.IOException;
33  import java.lang.reflect.Method;
34  import java.lang.reflect.InvocationTargetException;
35  import java.net.URL;
36  import java.net.MalformedURLException;
37  import java.util.Enumeration;
38  import java.util.Properties;
39  import java.util.Vector;
40  import sun.net.www.ParseUtil;
41  
42  /**
43   * The main entry point into AppletViewer.
44   */
45  public class Main {
46      /**
47       * The file which contains all of the AppletViewer specific properties.
48       */
49      static File theUserPropertiesFile;
50  
51      /**
52       * The default key/value pairs for the required user-specific properties.
53       */
54      static final String [][] avDefaultUserProps = {
55          // There's a bootstrapping problem here.  If we don't have a proxyHost,
56          // then we will not be able to connect to a URL outside the firewall;
57          // however, there's no way for us to set the proxyHost without starting
58          // AppletViewer.  This problem existed before the re-write.
59          {"http.proxyHost", ""},
60          {"http.proxyPort", "80"},
61          {"package.restrict.access.sun", "true"}
62      };
63  
64      static {
65          File userHome = new File(System.getProperty("user.home"));
66          // make sure we can write to this location
67          userHome.canWrite();
68  
69          theUserPropertiesFile = new File(userHome, ".appletviewer");
70      }
71  
72      // i18n
73      private static AppletMessageHandler amh = new AppletMessageHandler("appletviewer");
74  
75      /**
76       * Member variables set according to options passed in to AppletViewer.
77       */
78      private boolean debugFlag = false;
79      private boolean helpFlag  = false;
80      private String  encoding  = null;
81      private boolean noSecurityFlag  = false;
82      private static boolean cmdLineTestFlag = false;
83  
84      /**
85       * The list of valid URLs passed in to AppletViewer.
86       */
87      private static Vector urlList = new Vector(1);
88  
89      // This is used in init().  Getting rid of this is desirable but depends
90      // on whether the property that uses it is necessary/standard.
91      public static final String theVersion = System.getProperty("java.version");
92  
93      /**
94       * The main entry point into AppletViewer.
95       */
96      public static void main(String [] args) {
97          Main m = new Main();
98          int ret = m.run(args);
99  
100         // Exit immediately if we got some sort of error along the way.
101         // For debugging purposes, if we have passed in "-XcmdLineTest" we
102         // force a premature exit.
103         if ((ret != 0) || (cmdLineTestFlag))
104             System.exit(ret);
105     }
106 
107     private int run(String [] args) {
108         // DECODE ARGS
109         try {
110             if (args.length == 0) {
111                 usage();
112                 return 0;
113             }
114             for (int i = 0; i < args.length; ) {
115                 int j = decodeArg(args, i);
116                 if (j == 0) {
117                     throw new ParseException(lookup("main.err.unrecognizedarg",
118                                                     args[i]));
119                 }
120                 i += j;
121             }
122         } catch (ParseException e) {
123             System.err.println(e.getMessage());
124             return 1;
125         }
126 
127         // CHECK ARGUMENTS
128         if (helpFlag) {
129             usage();
130             return 0;
131         }
132 
133         if (urlList.size() == 0) {
134             System.err.println(lookup("main.err.inputfile"));
135             return 1;
136         }
137 
138         if (debugFlag) {
139             // START A DEBUG SESSION
140             // Given the current architecture, we will end up decoding the
141             // arguments again, but at least we are guaranteed to have
142             // arguments which are valid.
143             return invokeDebugger(args);
144         }
145 
146         // INSTALL THE SECURITY MANAGER (if necessary)
147         if (!noSecurityFlag && (System.getSecurityManager() == null))
148             init();
149 
150         // LAUNCH APPLETVIEWER FOR EACH URL
151         for (int i = 0; i < urlList.size(); i++) {
152             try {
153                 // XXX 5/17 this parsing method should be changed/fixed so that
154                 // it doesn't do both parsing of the html file and launching of
155                 // the AppletPanel
156                 AppletViewer.parse((URL) urlList.elementAt(i), encoding);
157             } catch (IOException e) {
158                 System.err.println(lookup("main.err.io", e.getMessage()));
159                 return 1;
160             }
161         }
162         return 0;
163     }
164 
165     private static void usage() {
166         System.out.println(lookup("usage"));
167     }
168 
169     /**
170      * Decode a single argument in an array and return the number of elements
171      * used.
172      *
173      * @param args The array of arguments.
174      * @param i    The argument to decode.
175      * @return     The number of array elements used when the argument was
176      *             decoded.
177      * @exception ParseException
178      *             Thrown when there is a problem with something in the
179      *             argument array.
180      */
181     private int decodeArg(String [] args, int i) throws ParseException {
182         String arg = args[i];
183         int argc = args.length;
184 
185         if ("-help".equalsIgnoreCase(arg) || "-?".equals(arg)) {
186             helpFlag = true;
187             return 1;
188         } else if ("-encoding".equals(arg) && (i < argc - 1)) {
189             if (encoding != null)
190                 throw new ParseException(lookup("main.err.dupoption", arg));
191             encoding = args[++i];
192             return 2;
193         } else if ("-debug".equals(arg)) {
194             debugFlag = true;
195             return 1;
196         } else if ("-Xnosecurity".equals(arg)) {
197             // This is an undocumented (and, in the future, unsupported)
198             // flag which prevents AppletViewer from installing its own
199             // SecurityManager.
200 
201             System.err.println();
202             System.err.println(lookup("main.warn.nosecmgr"));
203             System.err.println();
204 
205             noSecurityFlag = true;
206             return 1;
207         } else if ("-XcmdLineTest".equals(arg)) {
208             // This is an internal flag which should be used for command-line
209             // testing.  It instructs AppletViewer to force a premature exit
210             // immediately after the applet has been launched.
211             cmdLineTestFlag = true;
212             return 1;
213         } else if (arg.startsWith("-")) {
214             throw new ParseException(lookup("main.err.unsupportedopt", arg));
215         } else {
216             // we found what we hope is a url
217             URL url = parseURL(arg);
218             if (url != null) {
219                 urlList.addElement(url);
220                 return 1;
221             }
222         }
223         return 0;
224     }
225 
226     /**
227      * Following the relevant RFC, construct a valid URL based on the passed in
228      * string.
229      *
230      * @param url  a string which represents either a relative or absolute URL.
231      * @return     a URL when the passed in string can be interpreted according
232      *             to the RFC, <code>null</code> otherwise.
233      * @exception  ParseException
234      *             Thrown when we are unable to construct a proper URL from the
235      *             passed in string.
236      */
237     private URL parseURL(String url) throws ParseException {
238         URL u = null;
239         // prefix of the urls with 'file' scheme
240         String prefix = "file:";
241 
242         try {
243             if (url.indexOf(':') <= 1)
244             {
245                 // appletviewer accepts only unencoded filesystem paths
246                 u = ParseUtil.fileToEncodedURL(new File(url));
247             } else if (url.startsWith(prefix) &&
248                        url.length() != prefix.length() &&
249                        !(new File(url.substring(prefix.length())).isAbsolute()))
250             {
251                 // relative file URL, like this "file:index.html"
252                 // ensure that this file URL is absolute
253                 // ParseUtil.fileToEncodedURL should be done last (see 6329251)
254                 String path = ParseUtil.fileToEncodedURL(new File(System.getProperty("user.dir"))).getPath() +
255                     url.substring(prefix.length());
256                 u = new URL("file", "", path);
257             } else {
258                 // appletviewer accepts only encoded urls
259                 u = new URL(url);
260             }
261         } catch (MalformedURLException e) {
262             throw new ParseException(lookup("main.err.badurl",
263                                             url, e.getMessage()));
264         }
265 
266         return u;
267     }
268 
269     /**
270      * Invoke the debugger with the arguments passed in to appletviewer.
271      *
272      * @param args The arguments passed into the debugger.
273      * @return     <code>0</code> if the debugger is invoked successfully,
274      *             <code>1</code> otherwise.
275      */
276     private int invokeDebugger(String [] args) {
277         // CONSTRUCT THE COMMAND LINE
278         String [] newArgs = new String[args.length + 1];
279         int current = 0;
280 
281         // Add a -classpath argument that prevents
282         // the debugger from launching appletviewer with the default of
283         // ".". appletviewer's classpath should never contain valid
284         // classes since they will result in security exceptions.
285         // Ideally, the classpath should be set to "", but the VM won't
286         // allow an empty classpath, so a phony directory name is used.
287         String phonyDir = System.getProperty("java.home") +
288                           File.separator + "phony";
289         newArgs[current++] = "-Djava.class.path=" + phonyDir;
290 
291         // Appletviewer's main class is the debuggee
292         newArgs[current++] = "sun.applet.Main";
293 
294         // Append all the of the original appletviewer arguments,
295         // leaving out the "-debug" option.
296         for (int i = 0; i < args.length; i++) {
297             if (!("-debug".equals(args[i]))) {
298                 newArgs[current++] = args[i];
299             }
300         }
301 
302         // LAUNCH THE DEBUGGER
303         // Reflection is used for two reasons:
304         // 1) The debugger classes are on classpath and thus must be loaded
305         // by the application class loader. (Currently, appletviewer are
306         // loaded through the boot class path out of rt.jar.)
307         // 2) Reflection removes any build dependency between appletviewer
308         // and jdb.
309         try {
310             Class c = Class.forName("com.sun.tools.example.debug.tty.TTY", true,
311                                     ClassLoader.getSystemClassLoader());
312             Method m = c.getDeclaredMethod("main",
313                                            new Class[] { String[].class });
314             m.invoke(null, new Object[] { newArgs });
315         } catch (ClassNotFoundException cnfe) {
316             System.err.println(lookup("main.debug.cantfinddebug"));
317             return 1;
318         } catch (NoSuchMethodException nsme) {
319             System.err.println(lookup("main.debug.cantfindmain"));
320             return 1;
321         } catch (InvocationTargetException ite) {
322             System.err.println(lookup("main.debug.exceptionindebug"));
323             return 1;
324         } catch (IllegalAccessException iae) {
325             System.err.println(lookup("main.debug.cantaccess"));
326             return 1;
327         }
328         return 0;
329     }
330 
331     private void init() {
332         // GET APPLETVIEWER USER-SPECIFIC PROPERTIES
333         Properties avProps = getAVProps();
334 
335         // ADD OTHER RANDOM PROPERTIES
336         // XXX 5/18 need to revisit why these are here, is there some
337         // standard for what is available?
338 
339         // Standard browser properties
340         avProps.put("browser", "sun.applet.AppletViewer");
341         avProps.put("browser.version", "1.06");
342         avProps.put("browser.vendor", "Oracle Corporation");
343         avProps.put("http.agent", "Java(tm) 2 SDK, Standard Edition v" + theVersion);
344 
345         // Define which packages can be extended by applets
346         // XXX 5/19 probably not needed, not checked in AppletSecurity
347         avProps.put("package.restrict.definition.java", "true");
348         avProps.put("package.restrict.definition.sun", "true");
349 
350         // Define which properties can be read by applets.
351         // A property named by "key" can be read only when its twin
352         // property "key.applet" is true.  The following ten properties
353         // are open by default.  Any other property can be explicitly
354         // opened up by the browser user by calling appletviewer with
355         // -J-Dkey.applet=true
356         avProps.put("java.version.applet", "true");
357         avProps.put("java.vendor.applet", "true");
358         avProps.put("java.vendor.url.applet", "true");
359         avProps.put("java.class.version.applet", "true");
360         avProps.put("os.name.applet", "true");
361         avProps.put("os.version.applet", "true");
362         avProps.put("os.arch.applet", "true");
363         avProps.put("file.separator.applet", "true");
364         avProps.put("path.separator.applet", "true");
365         avProps.put("line.separator.applet", "true");
366 
367         // Read in the System properties.  If something is going to be
368         // over-written, warn about it.
369         Properties sysProps = System.getProperties();
370         for (Enumeration e = sysProps.propertyNames(); e.hasMoreElements(); ) {
371             String key = (String) e.nextElement();
372             String val = (String) sysProps.getProperty(key);
373             String oldVal;
374             if ((oldVal = (String) avProps.setProperty(key, val)) != null)
375                 System.err.println(lookup("main.warn.prop.overwrite", key,
376                                           oldVal, val));
377         }
378 
379         // INSTALL THE PROPERTY LIST
380         System.setProperties(avProps);
381 
382         // Create and install the security manager
383         if (!noSecurityFlag) {
384             System.setSecurityManager(new AppletSecurity());
385         } else {
386             System.err.println(lookup("main.nosecmgr"));
387         }
388 
389         // REMIND: Create and install a socket factory!
390     }
391 
392     /**
393      * Read the AppletViewer user-specific properties.  Typically, these
394      * properties should reside in the file $USER/.appletviewer.  If this file
395      * does not exist, one will be created.  Information for this file will
396      * be gleaned from $USER/.hotjava/properties.  If that file does not exist,
397      * then default values will be used.
398      *
399      * @return     A Properties object containing all of the AppletViewer
400      *             user-specific properties.
401      */
402     private Properties getAVProps() {
403         Properties avProps = new Properties();
404 
405         File dotAV = theUserPropertiesFile;
406         if (dotAV.exists()) {
407             // we must have already done the conversion
408             if (dotAV.canRead()) {
409                 // just read the file
410                 avProps = getAVProps(dotAV);
411             } else {
412                 // send out warning and use defaults
413                 System.err.println(lookup("main.warn.cantreadprops",
414                                           dotAV.toString()));
415                 avProps = setDefaultAVProps();
416             }
417         } else {
418             // create the $USER/.appletviewer file
419 
420             // see if $USER/.hotjava/properties exists
421             File userHome = new File(System.getProperty("user.home"));
422             File dotHJ = new File(userHome, ".hotjava");
423             dotHJ = new File(dotHJ, "properties");
424             if (dotHJ.exists()) {
425                 // just read the file
426                 avProps = getAVProps(dotHJ);
427             } else {
428                 // send out warning and use defaults
429                 System.err.println(lookup("main.warn.cantreadprops",
430                                           dotHJ.toString()));
431                 avProps = setDefaultAVProps();
432             }
433 
434             // SAVE THE FILE
435             try {
436                 FileOutputStream out = new FileOutputStream(dotAV);
437                 avProps.store(out, lookup("main.prop.store"));
438                 out.close();
439             } catch (IOException e) {
440                 System.err.println(lookup("main.err.prop.cantsave",
441                                           dotAV.toString()));
442             }
443         }
444         return avProps;
445     }
446 
447     /**
448      * Set the AppletViewer user-specific properties to be the default values.
449      *
450      * @return     A Properties object containing all of the AppletViewer
451      *             user-specific properties, set to the default values.
452      */
453     private Properties setDefaultAVProps() {
454         Properties avProps = new Properties();
455         for (int i = 0; i < avDefaultUserProps.length; i++) {
456             avProps.setProperty(avDefaultUserProps[i][0],
457                                 avDefaultUserProps[i][1]);
458         }
459         return avProps;
460     }
461 
462     /**
463      * Given a file, find only the properties that are setable by AppletViewer.
464      *
465      * @param inFile A Properties file from which we select the properties of
466      *             interest.
467      * @return     A Properties object containing all of the AppletViewer
468      *             user-specific properties.
469      */
470     private Properties getAVProps(File inFile) {
471         Properties avProps  = new Properties();
472 
473         // read the file
474         Properties tmpProps = new Properties();
475         try {
476             FileInputStream in = new FileInputStream(inFile);
477             tmpProps.load(new BufferedInputStream(in));
478             in.close();
479         } catch (IOException e) {
480             System.err.println(lookup("main.err.prop.cantread",
481                                       inFile.toString()));
482         }
483 
484         // pick off the properties we care about
485         for (int i = 0; i < avDefaultUserProps.length; i++) {
486             String value = tmpProps.getProperty(avDefaultUserProps[i][0]);
487             if (value != null) {
488                 // the property exists in the file, so replace the default
489                 avProps.setProperty(avDefaultUserProps[i][0], value);
490             } else {
491                 // just use the default
492                 avProps.setProperty(avDefaultUserProps[i][0],
493                                     avDefaultUserProps[i][1]);
494             }
495         }
496         return avProps;
497     }
498 
499     /**
500      * Methods for easier i18n handling.
501      */
502 
503     private static String lookup(String key) {
504         return amh.getMessage(key);
505     }
506 
507     private static String lookup(String key, String arg0) {
508         return amh.getMessage(key, arg0);
509     }
510 
511     private static String lookup(String key, String arg0, String arg1) {
512         return amh.getMessage(key, arg0, arg1);
513     }
514 
515     private static String lookup(String key, String arg0, String arg1,
516                                  String arg2) {
517         return amh.getMessage(key, arg0, arg1, arg2);
518     }
519 
520     class ParseException extends RuntimeException
521     {
522         public ParseException(String msg) {
523             super(msg);
524         }
525 
526         public ParseException(Throwable t) {
527             super(t.getMessage());
528             this.t = t;
529         }
530 
531         Throwable t = null;
532     }
533 }